﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Documents;
using System.Windows;
using System.Windows.Media;
using System.ComponentModel;
using System.Windows.Shapes;
using System.Windows.Controls;
using Pavilion.DesignerTool.ViewTools;
using Pavilion.DesignerTool.ViewTools.ConnectableListBox;


namespace Pavilion.DesignerTool
{
    public class Connection : FrameworkElement
    {
        public static readonly DependencyProperty StartProperty = DependencyProperty.Register( "Start", typeof(DragAwareListBoxItem), typeof(Connection),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnStartPropertyChanged)));
        public static readonly DependencyProperty EndProperty = DependencyProperty.Register("End", typeof(DragAwareListBoxItem), typeof(Connection),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnEndPropertyChanged)));
        public static readonly DependencyProperty LineColorProperty = DependencyProperty.Register("LineColor", typeof(Brush), typeof(Connection),
            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnLineColorChanged)));

        public DragAwareListBoxItem Start { get { return (DragAwareListBoxItem)base.GetValue(StartProperty); } set { base.SetValue(StartProperty, value); InvalidateVisual(); } }
        public DragAwareListBoxItem End { get { return (DragAwareListBoxItem)base.GetValue(EndProperty); } set { base.SetValue(EndProperty, value); } }
        public Brush LineColor { get { return (Brush)base.GetValue(LineColorProperty); } set { base.SetValue(LineColorProperty, value); InvalidateVisual(); } }

        private DragAwareListBoxItem ConnectionItem { get { return PavilionVisualTreeHelper.GetAncestor<DragAwareListBoxItem>(this); } }
        private LineGeometry Path { get { return path; } set { path = value; base.InvalidateVisual(); } }//forces redraw
        private LineGeometry path, lineArrow90, lineArrow180;
        
        public Connection()
        {
            base.Loaded += (sender, e) =>
            {
                this.DataContextChanged += new DependencyPropertyChangedEventHandler(Connection_DataContextChanged);
                ConnectionItem.OffsetChanged += x => CalculateNewPathGeometry();

                CalculateNewPathGeometry();
            };
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);
            DrawConnection(drawingContext);
        }

        private void DrawConnection(DrawingContext drawingContext)
        {
            var transParentBrush = new SolidColorBrush(Colors.Transparent);
            var pen = new Pen(LineColor, 1);
            var transparentPen = new Pen(transParentBrush, 6);
            drawingContext.DrawGeometry(LineColor, pen, lineArrow90);
            drawingContext.DrawGeometry(LineColor, pen, lineArrow180);
            drawingContext.DrawGeometry(LineColor, pen, Path);
            drawingContext.DrawGeometry(transParentBrush, transparentPen, Path);
        }

        private void CalculateNewPathGeometry()
        {
            if (Start == null || End == null)
                return;

            var startVector = new Microsoft.Xna.Framework.Vector2((float)((Point)Start.Position).X + ConnectionItem.Offset, (float)((Point)Start.Position).Y + ConnectionItem.Offset);
            var endVector = new Microsoft.Xna.Framework.Vector2((float)((Point)End.Position).X + ConnectionItem.Offset, (float)((Point)End.Position).Y + ConnectionItem.Offset);
            LineVector lineVector = new LineVector(startVector, endVector - startVector);

            Point endPosition = DetermineIntersectingPoint(lineVector, End);
            Point startPosition = DetermineIntersectingPoint(lineVector, Start);
            //endPosition.X += ConnectionItem.Offset;
            //endPosition.Y += ConnectionItem.Offset;
            //startPosition.X += ConnectionItem.Offset;
            //startPosition.Y += ConnectionItem.Offset;
            lineArrow90 = LineOfArrow(90, startPosition, endPosition);
            lineArrow180 = LineOfArrow(180, startPosition, endPosition);

            Path = new LineGeometry(startPosition, endPosition);
        }

        private Point DetermineIntersectingPoint(LineVector lineVector, FrameworkElement box)
        {
            var canvas = PavilionVisualTreeHelper.GetAncestor<Canvas>(box);

            Point point = box.TransformToAncestor(canvas).Transform(new Point(0, 0));
            var boxRectangle = new Microsoft.Xna.Framework.Rectangle((int)point.X, (int)point.Y, (int)box.ActualWidth, (int)box.ActualHeight);

            Microsoft.Xna.Framework.Vector2 intersectingPoint = LineVector.ObtainIntersactionPoint(lineVector, boxRectangle);

            return new Point((double)intersectingPoint.X, (double)intersectingPoint.Y);
        }

        private LineGeometry LineOfArrow(double theta, Point startPosition, Point endPosition)
        {
            LineGeometry line = new LineGeometry(new Point(0,0), new Point(10, 10));
            line.Transform = CreateAngleTransformation(theta, startPosition, endPosition);

            return line;
        }

        private TransformGroup CreateAngleTransformation(double theta, Point startPosition, Point endPosition)
        {
            var transformGroup = new TransformGroup();
            transformGroup.Children.Add(new RotateTransform(theta - ObtainAngleOfLine(startPosition, endPosition)));
            transformGroup.Children.Add(new TranslateTransform(endPosition.X, endPosition.Y));
            return transformGroup;
        }

        private double ObtainAngleOfLine(Point startPosition, Point endPosition)
        {
            double xDisplacement = endPosition.X - startPosition.X;
            double yDisplacement = -endPosition.Y + startPosition.Y;
            return RadianToDegree(Math.Atan2(yDisplacement, xDisplacement));
        }

        private double RadianToDegree(double angle)
        {
            return angle * 180 / 3.14;
        }

        private void UnRegisterListBoxItem(DragAwareListBoxItem listBoxItem)
        {
            if (listBoxItem == null)
                return;

            listBoxItem.PositionChanged -= new Action<DragAwareListBoxItem>(ListBoxItem_PositionChanged);
        }

        private void RegisterListBoxItem(DragAwareListBoxItem listBoxItem)
        {
            if (listBoxItem == null)
                return;

            listBoxItem.PositionChanged += new Action<DragAwareListBoxItem>(ListBoxItem_PositionChanged);
        }

        private void ListBoxItem_PositionChanged(DragAwareListBoxItem listBoxItem)
        {
            CalculateNewPathGeometry();
        }

        private void Connection_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (this.DataContext is IConnectonator)
                return;

            UnRegisterListBoxItem(Start);
            UnRegisterListBoxItem(End);
            Start = null;
            End = null;
        }

        private static void OnStartPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            Connection connection = (Connection)sender;
            connection.UnRegisterListBoxItem(args.OldValue as DragAwareListBoxItem);
            connection.RegisterListBoxItem(args.NewValue as DragAwareListBoxItem);
        }

        private static void OnEndPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            Connection connection = (Connection)sender;
            connection.UnRegisterListBoxItem(args.OldValue as DragAwareListBoxItem);
            connection.RegisterListBoxItem(args.NewValue as DragAwareListBoxItem);
        }

        private static void OnLineColorChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            (sender as Connection).InvalidateVisual();
        }
    }
}
